/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dataconn.jdbc.pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

import org.apache.dataconn.jdbc.pool.PoolProperties.InterceptorProperty;
import org.apache.log4j.Logger;
import org.lora.log4j.Log4jUtil;

/**
 * Abstract class that is to be extended for implementations of interceptors.
 * Everytime an operation is called on the {@link java.sql.Connection} object
 * the {@link #invoke(Object, Method, Object[])} method on the interceptor will
 * be called. Interceptors are useful to change or improve behavior of the
 * connection pool.<br/>
 * Interceptors can receive a set of properties. Each sub class is responsible
 * for parsing the properties during runtime when they are needed or simply
 * override the {@link #setProperties(Map)} method. Properties arrive in a
 * key-value pair of Strings as they were received through the configuration.
 * This method is called once per cached connection object when the object is
 * first configured.
 *
 * @author Filip Hanik
 * @version 1.0
 */
public abstract class JdbcInterceptor implements InvocationHandler {

	// logger
	protected static Logger log = Log4jUtil.getSystemLogger();
	/**
	 * {@link java.sql.Connection#close()} method name
	 */
	public static final String CLOSE_VAL = "close";
	/**
	 * {@link Object#toString()} method name
	 */
	public static final String TOSTRING_VAL = "toString";
	/**
	 * {@link java.sql.Connection#isClosed()} method name
	 */
	public static final String ISCLOSED_VAL = "isClosed";
	/**
	 * {@link javax.sql.PooledConnection#getConnection()} method name
	 */
	public static final String GETCONNECTION_VAL = "getConnection";
	/**
	 * {@link java.sql.Wrapper#unwrap(Class)} method name
	 */
	public static final String UNWRAP_VAL = "unwrap";
	/**
	 * {@link java.sql.Wrapper#isWrapperFor(Class)} method name
	 */
	public static final String ISWRAPPERFOR_VAL = "isWrapperFor";

	/**
	 * {@link java.sql.Connection#isValid(int)} method name
	 */
	public static final String ISVALID_VAL = "isValid";

	/**
	 * {@link java.lang.Object#equals(Object)}
	 */
	public static final String EQUALS_VAL = "equals";

	/**
	 * {@link java.lang.Object#hashCode()}
	 */
	public static final String HASHCODE_VAL = "hashCode";

	/**
	 * Properties for this interceptor.
	 */
	protected Map<String, InterceptorProperty> properties = null;

	/**
	 * The next interceptor in the chain
	 */
	private volatile JdbcInterceptor next = null;
	/**
	 * Property that decides how we do string comparison, default is to use
	 * {@link String#equals(Object)}. If set to <code>false</code> then the
	 * equality operator (==) is used.
	 */
	private boolean useEquals = true;

	/**
	 * Public constructor for instantation through reflection
	 */
	public JdbcInterceptor() {
		// NOOP
	}

	/**
	 * Gets invoked each time an operation on {@link java.sql.Connection} is
	 * invoked. {@inheritDoc}
	 */

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (getNext() != null)
			return getNext().invoke(proxy, method, args);
		else
			throw new NullPointerException();
	}

	/**
	 * Returns the next interceptor in the chain
	 * 
	 * @return the next interceptor in the chain
	 */
	public JdbcInterceptor getNext() {
		return next;
	}

	/**
	 * configures the next interceptor in the chain
	 * 
	 * @param next
	 */
	public void setNext(JdbcInterceptor next) {
		this.next = next;
	}

	/**
	 * Performs a string comparison, using references unless the useEquals
	 * property is set to true.
	 * 
	 * @param name1
	 * @param name2
	 * @return true if name1 is equal to name2 based on {@link #useEquals}
	 */
	public boolean compare(String name1, String name2) {
		if (isUseEquals()) {
			return name1.equals(name2);
		} else {
			return name1 == name2;
		}
	}

	/**
	 * Compares a method name (String) to a method (Method)
	 * {@link #compare(String,String)} Uses reference comparison unless the
	 * useEquals property is set to true
	 * 
	 * @param methodName
	 * @param method
	 * @return true if the name matches
	 */
	public boolean compare(String methodName, Method method) {
		return compare(methodName, method.getName());
	}

	/**
	 * Gets called each time the connection is borrowed from the pool This means
	 * that if an interceptor holds a reference to the connection the
	 * interceptor can be reused for another connection. <br/>
	 * This method may be called with null as both arguments when we are closing
	 * down the connection.
	 * 
	 * @param parent
	 *            - the connection pool owning the connection
	 * @param con
	 *            - the pooled connection
	 */
	public abstract void reset(ConnectionPool parent, PooledConnection con);

	/**
	 * Called when {@link java.sql.Connection#close()} is called on the
	 * underlying connection. This is to notify the interceptors, that the
	 * physical connection has been released. Implementation of this method
	 * should be thought through with care, as no actions should trigger an
	 * exception.
	 * 
	 * @param parent
	 *            - the connection pool that this connection belongs to
	 * @param con
	 *            - the pooled connection that holds this connection
	 * @param finalizing
	 *            - if this connection is finalizing. True means that the pooled
	 *            connection will not reconnect the underlying connection
	 */
	public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
	}

	/**
	 * Returns the properties configured for this interceptor
	 * 
	 * @return the configured properties for this interceptor
	 */
	public Map<String, InterceptorProperty> getProperties() {
		return properties;
	}

	/**
	 * Called during the creation of an interceptor The properties can be set
	 * during the configuration of an interceptor Override this method to
	 * perform type casts between string values and object properties
	 * 
	 * @param properties
	 */
	public void setProperties(Map<String, InterceptorProperty> properties) {
		this.properties = properties;
		final String useEquals = "useEquals";
		InterceptorProperty p = properties.get(useEquals);
		if (p != null) {
			setUseEquals(Boolean.parseBoolean(p.getValue()));
		}
	}

	/**
	 * @return true if the compare method uses the Object.equals(Object) method
	 *         false if comparison is done on a reference level
	 */
	public boolean isUseEquals() {
		return useEquals;
	}

	/**
	 * Set to true if string comparisons (for the
	 * {@link #compare(String, Method)} and {@link #compare(String, String)}
	 * methods) should use the Object.equals(Object) method The default is false
	 * 
	 * @param useEquals
	 */
	public void setUseEquals(boolean useEquals) {
		this.useEquals = useEquals;
	}

	/**
	 * This method is invoked by a connection pool when the pool is closed.
	 * Interceptor classes can override this method if they keep static
	 * variables or other tracking means around. <b>This method is only invoked
	 * on a single instance of the interceptor, and not on every instance
	 * created.</b>
	 * 
	 * @param pool
	 *            - the pool that is being closed.
	 */
	public void poolClosed(ConnectionPool pool) {
		// NOOP
	}

	/**
	 * This method is invoked by a connection pool when the pool is first
	 * started up, usually when the first connection is requested. Interceptor
	 * classes can override this method if they keep static variables or other
	 * tracking means around. <b>This method is only invoked on a single
	 * instance of the interceptor, and not on every instance created.</b>
	 * 
	 * @param pool
	 *            - the pool that is being closed.
	 */
	public void poolStarted(ConnectionPool pool) {
		// NOOP
	}

}
